package com.cdks.wuxiu.api.component.aspect;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;

import com.cdks.wuxiu.api.component.annotation.IgnoreSign;
import com.cdks.wuxiu.api.component.annotation.ThirdParty;
import com.cdks.wuxiu.api.model.common.AuthParam;
import com.cdks.wuxiu.api.model.common.CommonHeader;
import com.cdks.wuxiu.api.model.common.CommonParam;
import com.cdks.wuxiu.api.model.vo.AppVersionInfoVO;
import com.cdks.wuxiu.api.service.UserService;
import com.cdks.wuxiu.api.service.AccountService;
import com.cdks.wuxiu.api.service.AuthService;
import com.cdks.wuxiu.api.service.SystemService;
import com.cdks.wuxiu.api.util.AuthUtils;
import com.cdks.wuxiu.api.util.TimeUtils;
import com.cdks.wuxiu.common.component.constant.ApiCodeEnum;
import com.cdks.wuxiu.common.config.AuthProps;
import com.cdks.wuxiu.common.exception.ApiException;
import com.cdks.wuxiu.common.util.ServletUtils;
import com.cdks.wuxiu.entity.mysql.AppVersionConfig;
import com.cdks.wuxiu.entity.mysql.BannedUserInfo;
import com.cdks.wuxiu.entity.mysql.UserBaseInfo;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 统一身份认证切面
 */
@Slf4j
@Aspect
@Component
public class AuthAspect {
    /**
     * Auth 配置参数类
     */
    @Resource
    private AuthProps authProps;
    /**
     * Security 认证服务类
     */
    @Resource
    private AuthService authService;   
    @Resource
    private AccountService accountService;
    @Resource
    private UserService userService;   
    @Resource
    private SystemService systemService;
    @Value("${spring.profiles.active}")
    private String env;

    
    @Around("execution(public * com.cdks.wuxiu.api.controller..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request  = ServletUtils.getRequest();
        MethodSignature signature  = (MethodSignature) joinPoint.getSignature();
        ThirdParty thirdParty  = signature.getMethod().getAnnotation(ThirdParty.class);
        if (thirdParty != null)
        {
            Object result = joinPoint.proceed(joinPoint.getArgs());
            log.info("接口返回 - " + request.getRequestURI() + " : " + JSONUtil.toJsonStr(result));
    
            return result;
        }

        CommonHeader commonHeader = AuthUtils.getCommonHeader();
        if ((commonHeader.getBranch() == null) || (commonHeader.getSerial() == null) || (commonHeader.getSign() == null)) {
            throw new ApiException(ApiCodeEnum.PARAM_ERROR);
        }

        // 判断是否需要校验 token
        UserBaseInfo userInfo  = null;
        AuthParam authParam  = BeanUtil.toBean(getNameAndValue(joinPoint), AuthParam.class);
        Long userId  = authParam.getUserId();
        String token  = authParam.getToken();
        if ((userId != null) && (token != null))
        {
            // 本地开发环境不校验token
            if (!("dev".equals(env)))
            {
                authService.checkUserToken(userId, authParam.getNonce(), token);
            }
        
            // 检查用户账号状态
            userInfo  = userService.getUserById(userId);
            if (userInfo == null)
            {
                throw new ApiException(ApiCodeEnum.ACCOUNT_ERROR);
            }

            BannedUserInfo info  = accountService.checkAccount(userId, authParam.getUdid());
            if (info != null)
            {
                log.info("checkAccount: " + info);

                //永久封禁
                if (info.getExpire() == 0)
                {
                    throw new ApiException(10, "您的账号因" + info.getReason() + "已被封禁，若有疑问请咨询官方微信：xinsuwx");
                }

                //解封时间未到
                if (info.getExpire() > TimeUtils.getTimeSecond())
                {
                    throw new ApiException(10, "您的账号因" + info.getReason() + "已被封禁，" + TimeUtils.getLocalDateTime("yyyy-MM-dd HH:mm:ss", info.getExpire()) + " 前不可使用。若有疑问请咨询官方微信：xinsuwx");
                }

                throw new ApiException(ApiCodeEnum.ACCOUNT_ERROR);
            }
        }
       
        // 跳过不验签接口
        IgnoreSign ignoreSign  = signature.getMethod().getAnnotation(IgnoreSign.class);
        // 本地开发环境不校验签名
        if ((!("dev".equals(env))) && (ignoreSign == null))
        {
            // 校验参数签名
            String body  = ServletUtil.getBody(request);
            String sign  = AuthUtils.signParam(JSONUtil.toBean(body, Map.class));

            if (!sign.equals(commonHeader.getSign()))
            {
                log.info("参数签名：header = " + commonHeader.getSign());

                throw new ApiException(ApiCodeEnum.PARAM_ERROR.getCode(), "签名错误");
            }
        }

        // 检查是否需要强制升级
        AppVersionConfig config  = systemService.getVersionByBranch(commonHeader.getBranch());
        if ((config != null) && (commonHeader.getSerial() < config.getMinSerial())) {
            throw new ApiException(ApiCodeEnum.INVALID_VERSION, BeanUtil.copyProperties(config, AppVersionInfoVO.class));
        }

        Object[] params = joinPoint.getArgs();
        CommonParam param  = (CommonParam)params[params.length - 1];
        param.setBranch(commonHeader.getBranch());
        param.setSerial(commonHeader.getSerial());
        param.setUserInfo(userInfo);

        Object result = joinPoint.proceed(params);
        log.info("接口返回 - " + request.getRequestURI() + " : " + JSONUtil.toJsonStr(result));

        return result;
    }

    /**
     * 获取切入方法参数
     */
    Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) {
        Map<String, Object> param = new HashMap<>();
        Object[] paramValues = joinPoint.getArgs();

        for (Object paramValue : paramValues) {
            Map<String, Object> map = BeanUtil.beanToMap(paramValue);
            param.putAll(map);
        }

        return param;
    }

    /**
     * 忽略认证的接口不执行过滤
     */
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        
        if (ObjectUtil.isNull(httpMethod)) {
            httpMethod = HttpMethod.GET;
        }

        Set<String> ignores = new HashSet<>();
        switch (httpMethod) {
            case GET:
                ignores.addAll(authProps.getIgnore().getGet());
                break;
            case POST:
                ignores.addAll(authProps.getIgnore().getPost());
                break;
            case PUT:
                ignores.addAll(authProps.getIgnore().getPut());
                break;
            case DELETE:
                ignores.addAll(authProps.getIgnore().getDelete());
                break;
            default:
                break;
        }

        ignores.addAll(authProps.getIgnore().getPattern());
        if (CollUtil.isNotEmpty(ignores)) {
            for (String ignore : ignores) {
                AntPathRequestMatcher matcher = new AntPathRequestMatcher(ignore, method);
                if (matcher.matches(request)) {
                    return true;
                }
            }
        }

        return false;
    }
}
